import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import { SpinnerModule } from './spinner.module';

@Component({
  template: `
    <lv-spinner
      [lvMin]="lvMin"
      [lvMax]="lvMax"
      [lvStep]="lvStep"
      [lvSize]="lvSize"
      [lvRequired]="lvRequired"
      [lvDisabled]="lvDisabled"
      [lvReadOnly]="lvReadOnly"
      [lvOnlyInput]="lvOnlyInput"
      [lvPlaceHolder]="lvPlaceHolder"
      [(ngModel)]="value"
      (ngModelChange)="modelChange(value)"
    ></lv-spinner>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestSpinnerComponent {
  @Input() lvMin: number;
  @Input() lvMax: number;
  @Input() lvStep: number = 1;
  @Input() lvSize: 'default' | 'large' = 'default';
  @Input() lvRequired: boolean = true;
  @Input() lvDisabled: boolean = false;
  @Input() lvReadOnly: boolean = false;
  @Input() lvOnlyInput: boolean = false;
  @Input() lvPlaceHolder: boolean = false;

  value = 10;

  modelChange(): void {}
}

describe('SpinnerComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestSpinnerComponent],
      imports: [SpinnerModule, FormsModule],
      teardown: { destroyAfterEach: true },
    }).compileComponents();
  }));

  describe('render', () => {
    let component: TestSpinnerComponent;
    let fixture: ComponentFixture<TestSpinnerComponent>;

    const delay = (): void => {
      tick(500);
      fixture.detectChanges();
    };

    const renderComponent = (para): void => {
      for (const [key, value] of Object.entries(para)) {
        component[key] = value;
      }
      delay();
      delay();
      expect(fixture).toMatchSnapshot();
    };

    beforeEach(() => {
      fixture = TestBed.createComponent(TestSpinnerComponent);
      component = fixture.componentInstance;
    });

    it('with lvSize', fakeAsync(() => {
      renderComponent({ lvSize: 'large' });
      renderComponent({ lvSize: 'default' });
    }));

    it('with lvOnlyInput', fakeAsync(() => {
      renderComponent({ lvOnlyInput: true });
    }));

    it('with lvPlaceHolder', fakeAsync(() => {
      renderComponent({ lvPlaceHolder: 'Please input.' });
    }));

    it('with lvReadOnly', fakeAsync(() => {
      renderComponent({ lvReadOnly: true });
    }));
  });

  describe('work', () => {
    let component: TestSpinnerComponent;
    let fixture: ComponentFixture<TestSpinnerComponent>;

    const delay = (): void => {
      tick(500);
      fixture.detectChanges();
    };

    const renderComponent = (param): void => {
      for (const [key, value] of Object.entries(param)) {
        component[key] = value;
      }
      delay();
      delay();
    };
    const getInput = (): any => fixture.nativeElement.querySelector('.lv-spinner-input');

    beforeEach(() => {
      fixture = TestBed.createComponent(TestSpinnerComponent);
      component = fixture.componentInstance;
    });

    it('should work with lvRequired', fakeAsync(() => {
      fixture.detectChanges();
      getInput().value = '';
      tick(500);
      getInput().dispatchEvent(new Event('input'));
      delay();
      expect(getInput().value).toEqual('');
      expect(component.value).toBeFalsy();

      getInput().dispatchEvent(new Event('blur'));
      delay();
      delay();
      expect(component.value).toEqual(10);
    }));

    it('should work without lvRequired', fakeAsync(() => {
      renderComponent({ value: '', lvRequired: false });
      expect(component.value).toEqual('');

      getInput().dispatchEvent(new Event('blur'));
      delay();
      expect(getInput().value).toEqual('');
      expect(component.value).toBeFalsy();
    }));

    it('should work with lvMin and lvMax', fakeAsync(() => {
      renderComponent({ lvMin: 5, lvMax: 100, value: 0 });
      delay();
      getInput().dispatchEvent(new Event('blur'));
      delay();
      expect(component.value).toEqual(5);

      getInput().value = 110;
      delay();
      getInput().dispatchEvent(new Event('input'));
      delay();
      getInput().dispatchEvent(new Event('blur'));
      delay();
      delay();
      expect(component.value).toEqual(100);
    }));

    it('should work with lvStep', fakeAsync(() => {
      renderComponent({ lvStep: 2 });
      const addBtn = fixture.nativeElement.querySelector('.lv-spinner-plus-button');
      const reduceBtn = fixture.nativeElement.querySelector('.lv-spinner-less-button');
      addBtn.dispatchEvent(new Event('mousedown'));
      addBtn.dispatchEvent(new Event('mouseup'));
      delay();
      expect(component.value).toEqual(12);
      reduceBtn.dispatchEvent(new Event('mousedown'));
      reduceBtn.dispatchEvent(new Event('mouseup'));
      delay();
      expect(component.value).toEqual(10);

      getInput().dispatchEvent(
        new KeyboardEvent('keydown', {
          keyCode: UP_ARROW,
          bubbles: true,
        }),
      );
      delay();
      expect(component.value).toEqual(12);
      getInput().dispatchEvent(
        new KeyboardEvent('keydown', {
          keyCode: DOWN_ARROW,
          bubbles: true,
        }),
      );
      delay();
      expect(component.value).toEqual(10);
    }));

    it('should not work with lvDisabled', fakeAsync(() => {
      renderComponent({ lvDisabled: true });
      const input = getInput();

      input.value = 5;
      input.dispatchEvent(new Event('input'));
      fixture.detectChanges();
      expect(input.textContent).not.toBe(5);

      getInput().dispatchEvent(
        new KeyboardEvent('keydown', {
          keyCode: UP_ARROW,
          bubbles: true,
        }),
      );
      delay();
      expect(input.textContent).not.toEqual(11);
      getInput().dispatchEvent(
        new KeyboardEvent('keydown', {
          keyCode: DOWN_ARROW,
          bubbles: true,
        }),
      );
      delay();
      expect(input.textContent).not.toEqual(9);
    }));

    it('should work with maximum', fakeAsync(() => {
      fixture.detectChanges();
      // eslint-disable-next-line
      getInput().value = 11111111111111111111;
      tick(500);
      getInput().dispatchEvent(new Event('input'));
      delay();
      tick(500);
      getInput().dispatchEvent(new Event('blur'));
      delay();
      expect(component.value).toEqual(Number.MAX_SAFE_INTEGER);

      // eslint-disable-next-line
      getInput().value = -11111111111111111111;
      tick(500);
      getInput().dispatchEvent(new Event('input'));
      delay();
      tick(500);
      getInput().dispatchEvent(new Event('blur'));
      delay();
      expect(component.value).toEqual(-Number.MAX_SAFE_INTEGER);
    }));

    it('should work with input minus sign', fakeAsync(() => {
      fixture.detectChanges();
      getInput().value = '-';
      tick(500);
      getInput().dispatchEvent(new Event('input'));
      delay();
      expect(getInput().value).toEqual('-');
    }));
  });
});
